import Vue from 'vue'
import store from '@/store'
import { isString, isArray } from '@/utils/validate'
import { parseTime } from '@/utils'
import settings from '@/settings'

const { errorLog: needErrorLog } = settings

/**
 * 全局异常捕获
 * @param {Object} err error对象
 * @param {Object} vm Vue实例 | undefined
 * @param {String} info Vue特定的错误信息，比如错误所在的生命周期钩子 | undefined
 */
const errorHandler = (err, vm, info) => {
  // a hack https://forum.vuejs.org/t/dispatch-in-vue-config-errorhandler-has-some-problem/23500
  Vue.nextTick(() => {
    store.dispatch('errorLog/addErrorLog', {
      err,
      vm: vm || {
        $vnode: { tag: '' }
      },
      info: info || (err.config && err.request) ? err.config.url : '',
      url: window.location.href,
      time: parseTime(Date.now())
    })
    console.error(err, info)
  })
}

const checkNeed = () => {
  const env = process.env.NODE_ENV
  if (isString(needErrorLog)) {
    return env === needErrorLog
  }
  if (isArray(needErrorLog)) {
    return needErrorLog.includes(env)
  }
  return false
}

const isPromise = (ret) => {
  return (ret && typeof ret.then === 'function' && typeof ret.catch === 'function')
}

const registerActionHandle = (actions) => {
  Object.keys(actions).forEach(key => {
    const fn = actions[key]
    actions[key] = function(...args) {
      const ret = fn.apply(this, args)
      if (isPromise(ret)) {
        return ret.catch(errorHandler)
      } else { // 默认错误处理
        return ret
      }
    }
  })
}

const registerVuex = (instance) => {
  if (instance.$options['store']) {
    const actions = instance.$options['store']['_actions'] || {}
    if (actions) {
      const tempActions = {}
      Object.keys(actions).forEach(key => {
        tempActions[key] = actions[key][0]
      })
      registerActionHandle(tempActions)
    }
  }
}

const registerVue = (instance) => {
  if (instance.$options.methods) {
    const actions = instance.$options.methods || {}
    if (actions) {
      registerActionHandle(actions)
    }
  }
}

if (checkNeed()) {
  Vue.config.errorHandler = errorHandler
  Vue.mixin({
    beforeCreate() {
      registerVue(this)
      registerVuex(this)
    }
  })
  Vue.prototype.$throw = errorHandler
}
